/*
	PCI_64.c

	contains functionality to enumerate the PCI-Express bus and generate device allocation tables
	and a bus topology tree

	Author: Aidan Goddard 25/1/14
*/

#include"headers/printf.h"
#include"../kernel/headers/ACPI_tables.h"
#include"../kernel/headers/kernel_tables.h"
#include"../kernel/headers/types.h"
#include"../kernel/headers/scheduler_functions.h"

//
struct __attribute__((__packed__,aligned(4096))) PCIE_CONFIG
{
	volatile uint32_t	vendor_id : 16;
	volatile uint32_t 	device_id : 16;

	volatile uint32_t	command : 16;
	volatile uint32_t 	status : 16;

	volatile uint32_t	revision_id : 8;
	volatile uint32_t 	class_code : 24;

	volatile uint32_t	cache_line_size : 8;
	volatile uint32_t 	master_latency_timer : 8;
	volatile uint32_t 	header_type : 8;
	volatile uint32_t 	BIST : 8;

	volatile uint32_t 	reserved0;

	volatile uint32_t 	reserved1;

	volatile uint32_t 	primary_bus : 8;
	volatile uint32_t 	secondary_bus : 8;
	volatile uint32_t 	subordinate_bus : 8;
	volatile uint32_t 	secondary_latency : 8;

	volatile uint32_t 	reserved2[6];

	volatile uint32_t 	cap_ptr : 8;
	volatile uint32_t 	reserved3 : 24;

	volatile uint32_t 	reserved4;

	volatile uint32_t 	interrupt_line : 8;
	volatile uint32_t 	interrupt_pin : 8;
	volatile uint32_t 	reserved5 : 16;
};

// function to generate a present device list
static void GetDevices()
{
	// get pointer to device enumeration table
	struct PCIE_DEVICES *devices = (struct PCIE_DEVICES*)(PCI_KOFFSET);
	uint32_t k = 0;		// device list iterator

	// zero first entry
	devices[0].handle = 0;

	// get ACPI_MCFG table
	ACPI_MCFG *mcfg = ACPIGetMCFGBase();
	if(mcfg == NULL)
	{
		return;
	}

	// have mcfg table
	// get entry count
	uint32_t memory_spaces = (mcfg->length - 44) / 16;

	// go through each memory space entry in the table
	uint32_t m, b, d, f;
	for(m = 0; m < memory_spaces; m++)
	{
		// get base address of PCIE config area
		struct PCIE_CONFIG *pcie = (struct PCIE_CONFIG*)(mcfg->entries[m].config_base_address);

		// get base of iterator
		uint32_t i = 0;

		// get start and end bus
		uint32_t start_bus = mcfg->entries[m].start_PCI_bus_number;
		uint32_t end_bus = mcfg->entries[m].end_PCI_bus_number;

		// report this information
		printf("\n[PCIINIT] memory space %u: start bus %u end bus %u", m, start_bus, end_bus);

		// go through all of the buses in this memory space
		for(b = start_bus; b <= end_bus; b++)
		{
			// for each device in the bus
			for(d = 0; d < 32; d++)
			{
				// for each function in the bus
				for(f = 0; f < 8; f++)
				{
					// check vendor id is valid
					uint16_t vendor_id = pcie[i].vendor_id;
					if(vendor_id == 0xffff)
					{
						i++;
						continue;
					}

					// get the class code
					uint32_t class_code = pcie[i].class_code;

					// report
					printf("\n[PCIINIT] bus %u device %u function %u", b, d, f);
					printf(" class 0x%x", class_code);

					// get and save details
					devices[k].bus = b;
					devices[k].device = d;
					devices[k].function = f;
					devices[k].class_code = class_code;
					devices[k].vendor_id = vendor_id;
					devices[k].device_id = pcie[i].device_id;
					devices[k].IRQ_pin = pcie[i].interrupt_pin;
					devices[k].handle = (k & 0xffff) | (GetCPUTimeStamp() << 16);
					devices[k].config_base_address = (uint64_t)&pcie[i];

					// report handle
					printf(" at 0x%a", devices[k].config_base_address);

					// increase iterators
					k++;
					i++;

				}
			}
		}
	}

	// set last device as unused if list not full (I doubt the list will ever be full)
	if(k <= 0xffff)
	{
		devices[k].handle = 0;
	}

	// finished
	return;
}

// function to reserve all bridges and host adapters
static void ReserveBridges()
{
	// get pointer to devices and reservations table
	struct PCIE_DEVICES *devices = (struct PCIE_DEVICES*)(PCI_KOFFSET);
	struct PCIE_DEVICES_RESERVATIONS *res = (struct PCIE_DEVICES_RESERVATIONS*)(PCI_KOFFSET + 0x200000);

	// check each device's class code
	uint32_t i = 0;
	while(devices[i].handle != 0 && i < 0x10000)
	{
		// get the class code
		uint32_t class_code = devices[i].class_code;

		// check the main code
		class_code = class_code >> 16;
		class_code = class_code & 0xff;
		if(class_code == 0x06)
		{
			// main code is a bridge
			// reserve it for system use
			res[i].is_reserved = 1;
			res[i].key = GetCPUTimeStamp();
			res[i].reserved_by = 0xffffffff;
		}

		// increase iterator
		i++;
	}

	// finished
	return;
}


// top level function to enumerate the PCIE bus and generate various required system tables
void EnumeratePCIEBus()
{
	// get pointer to reservations table
	struct PCIE_DEVICES_RESERVATIONS *res = (struct PCIE_DEVICES_RESERVATIONS*)(PCI_KOFFSET + 0x200000);

	// construct reservations table
	uint32_t i;
	for(i = 0; i < 0x10000; i++)
	{
		res[i].is_reserved = 0;
		res[i].key = 0;
		res[i].reserved_by = 0;
	}

	// get list of devices
	GetDevices();

	// get pointer to device enumeration table
	struct PCIE_DEVICES *devices = (struct PCIE_DEVICES*)(PCI_KOFFSET);

	// check that devices were detected
	if(devices[0].handle == 0)
	{
		return;
	}

	// reserve all bridges and host adapters
	ReserveBridges();

	//printf("\n%u", sizeof(struct PCIE_DEVICES));
	//printf("\n%u", sizeof(struct PCIE_DEVICES_RESERVATIONS));

	// finished
	return;
}


